package modeller.queueingnetworksmodel.structure;

import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.servicedemand.transaction.TransactionServiceDemand;
import modeller.databasedesignmodel.transaction.Transaction;
import modeller.queueingnetworksmodel.probabilities.TransactionRoutingMatrix;
import org.jdom2.Element;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import static queped.quepedconstants.Strings.*;
import static queped.quepedconstants.Strings.JMTExponentialClasspath;
import static queped.quepedconstants.Strings.XML_A_EXPONENTIAL;

/**
 * Created by:  Jason Ye
 * Date:        30/05/2012
 * Time:        20:50
 */
public class SourceNode extends AbstractNodeChildElement {

    private Element queueElement;
    private Element psServerElement;
    private Element routerElement;

    private Relation router;

    public SourceNode(TransactionRoutingMatrix qnInstantaneousTransitionMatrix, HashSet<Transaction> transactions) {

        super(transactions, new Relation(2, 2, 2, 2));

        relation.setRelationName("Source 0");
        this.router = new Relation(2, 2, 2, 2);
        router.setRelationName("Router");
        // init child elems
        this.queueElement = generateSourceQueueElement();

        this.psServerElement = generateSourcePsServerElement();
        this.routerElement = generateSourceRouterElement();

    }

    private Element generateSourceRouterElement() {

        Element routerSection = new Element("section");
        routerSection.setAttribute("className","Router");
        routerSection.addContent(generateRoutingStrat());

        return routerSection;
    }

    private Element generateRoutingStrat() {

        Element routingStrat = generateNewParam(JMTROUTINGSTRAT, XML_A_ROUTINGSTRATEGY, true);

        // for each transaction
        for (Transaction transaction : transactions) {

            if (transaction.isOpen()) {
                Element probabilitiesForTransaction = generateSubParam(JMTEMPIRCALSTRAT, XML_A_SUBPARAMETER_PROBABILITIES);


                Element empiricalEntryArray = generateSubParam(JMTEMPIRCALENTRY, XML_A_SUBPARAMETER_EMPIRICAL_ENTRYARRAY, true);

                // tell the loop outsider to add a empirical entry array for this transaction from source to router

                empiricalEntryArray.addContent(generateEmpiricalEntryElem(router, 1));

                Element transactionRefClass = generateNewRefClassElem(transaction.getTransactionName());

                probabilitiesForTransaction.addContent(empiricalEntryArray);
                routingStrat.addContent(transactionRefClass);
                routingStrat.addContent(probabilitiesForTransaction);

            }

        }

        return routingStrat;
    }

    private Element generateEmpiricalEntryElem(Relation dest, double pi) {

        //init element
        Element empiricalEntry = generateSubParam(JMTEMPIRCALENTRY, XML_A_SUBPARAMETER_EMPIRICAL_ENTRY);

        //station name
        empiricalEntry.addContent(generateEmpiricalEntryStationName(dest));
        //probability
        empiricalEntry.addContent(generateEmpiricalEntryProbElem(pi));
        return empiricalEntry;
    }

    private Element generateEmpiricalEntryProbElem(double pi) {

        Element p = generateSubParam(DOUBLECLASSPATH, XML_A_SUBPARAMETER_PROBABILITY);

        p.addContent(generateValueElem(Double.toString(pi)));

        return p;
    }

    private Element generateEmpiricalEntryStationName(Relation dest) {

        Element stationName = generateSubParam(STRINGCLASSPATH, XML_A_PRELOADSTATION_NAME);

        Element value = generateValueElem(dest.getRelationName());

        stationName.addContent(value);

        return stationName;
    }

    private Element generateSourcePsServerElement() {

        Element psServer = new Element(XML_E_STATION_SECTION);

        psServer.setAttribute(XML_A_STATION_SECTION_CLASSNAME, CLASSNAME_TUNNEL);

        return psServer;

    }

    private Element generateSourceQueueElement() {

        Element randomsourceElem = new Element("section");
        randomsourceElem.setAttribute(XML_A_STATION_SECTION_CLASSNAME, "RandomSource");

        Element serviceStrat = generateServiceStrategy();

        randomsourceElem.addContent(serviceStrat);


        return randomsourceElem;

    }

    @Override
    public Element toXMLRepresentation() {

        Element result = new Element("node");
        result.setAttribute("name", relation.getRelationName());

        result.addContent(queueElement);
        result.addContent(psServerElement);
        result.addContent(routerElement);

        return result;
    }

    private Element generateServiceStrategy() {

        Element serviceStrategy = generateNewParam(JMTSERVICESTRAT, XML_A_SERVICESTRAT, true);

        // iterate through  each transaction, add arrival rate if transaction is open
        for (Transaction t : transactions) {

            if (t.isOpen()) {

                double rate = t.getTransactionPrevalence();

                //add ref class element
                serviceStrategy.addContent(generateNewRefClassElem(t.getTransactionName()));
                // add service time element
                serviceStrategy.addContent(generateServiceTimeStrategy(rate));
            }

        }


        return serviceStrategy;

    }

    private Element generateServiceTimeStrategy(double rate) {

        Element serviceTimeStrat = generateSubParam(JMTSERVICETIMESTRAT, XML_A_SERVICETIMESTRAT);


        serviceTimeStrat.addContent(generateExponentialSubParam());
        serviceTimeStrat.addContent(generateDistrParSubParam(rate));

        return serviceTimeStrat;


    }

    private Element generateDistrParSubParam(double rate) {

        Element distrPar = generateSubParam(JMTExponentialParClasspath, XML_A_DISTRPARAM);

        Element lambda = generateSubParam(DOUBLECLASSPATH, "lambda");

        // lambda is equal to the inverse of mean service time(demand) calculated
        lambda.addContent(generateValueElem(String.format("%10f", 1 / rate)));

        distrPar.addContent(lambda);

        return distrPar;

    }

    private Element generateExponentialSubParam() {
        return generateSubParam(JMTExponentialClasspath, XML_A_EXPONENTIAL);
    }

}
